#include "VoronoiRenderer.h"
#include <string>
#include <limits>
#include <algorithm>
#include "../../geom/Triangle.h"
#include "../../geom/Vector3D.h"
#include "../ui/triangulationprogessdialog.h"

using namespace voronoi;

VoronoiRenderer::VoronoiRenderer(const geom::TerrainPointsList &points, int width, int height) : m_points(points), m_width(width), m_height(height)
{
    imageRendered = false;

    GLint maxTexSize;
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
    printf("Max texture size: %u\n",maxTexSize);


    m_fbo.init(width,height,GL_RGBA32F_ARB,false);

    renderedImage = new GLfloat [4 * m_width * m_height];
    convertedImage = new unsigned int[m_width*m_height];

    m_shader = new glutil::ShaderProgram();
    m_shader->attachShaderFromFile(GL_VERTEX_SHADER, "data/shader.vert");
    m_shader->attachShaderFromFile(GL_FRAGMENT_SHADER, "data/shader.frag");
    m_shader->link();
}

VoronoiRenderer::~VoronoiRenderer()
{
    m_fbo.release();
    delete m_shader;
    delete[] renderedImage;
    delete[] convertedImage;
}

void VoronoiRenderer::getTriangulated(std::vector<util::Triplet<unsigned int> > * triangles, std::map<unsigned int, geom::Vector3D> * normals)
{
    std::set<Face>::const_iterator it = m_parsedFaces.begin();
    std::set<Face>::const_iterator end = m_parsedFaces.end();
    for(;it!=end;++it)
    {
        std::vector<unsigned int> points;
        Face::const_iterator f_it = (*it).begin();
        Face::const_iterator f_end = (*it).end();

        geom::Point3D summedCoords(0,0,0);
        int numPoints = 0;
        for(;f_it!=f_end;++f_it) // each face has several points
        {
            if ((*f_it) == 2113664)
                continue; // it's the background

            const geom::Point3D & p = m_points.at(*f_it).p;
            points.push_back(*f_it);

            summedCoords.x+=p.x;
            summedCoords.y+=p.y;
            summedCoords.z+=p.z;
            ++numPoints;
        }

        geom::Point3D middlePoint;
        middlePoint.x = summedCoords.x/numPoints;
        middlePoint.y = summedCoords.y/numPoints;
        middlePoint.z = summedCoords.z/numPoints;

        PointComparator comp(m_points,middlePoint); // we order the points around the middle one
        std::sort(points.begin(), points.end(), comp);

        // If it is a convex polygon (and we work under that assumption), we can triangulate it in a fan,
        numPoints = points.size();
        //	std::multimap<unsigned int, geom::Vector3D> normalsPerPoint;
        for(int i=2;i<numPoints; ++i) // If the "face" has less than 3 points, it obviates it
        {
            util::Triplet<unsigned int> tri;
            tri[0] = points.at(0);
            tri[1] = points.at(i-1);
            tri[2] = points.at(i);

            triangles->push_back(tri);

            /*     geom::Triangle gtri(m_points[tri[0]].p,m_points[tri[1]].p,m_points[tri[2]].p);
                        geom::Vector3D n = gtri.getNormal() * gtri.getArea();

                        normalsPerPoint.insert(std::pair<unsigned int,geom::Vector3D>(tri[0], n));
                        normalsPerPoint.insert(std::pair<unsigned int,geom::Vector3D>(tri[1], n));
                        normalsPerPoint.insert(std::pair<unsigned int,geom::Vector3D>(tri[2], n));
            */
        }
#if false
        while(normalsPerPoint.size()>0)
        {
            typedef std::multimap<unsigned int, geom::Vector3D> VectorsMMap;
            VectorsMMap::const_iterator it = normalsPerPoint.begin();
            unsigned int pointID = it->first;

            std::pair<VectorsMMap::iterator,VectorsMMap::iterator> range = normalsPerPoint.equal_range(pointID);

            geom::Vector3D sum(0,0,0);
            for(it=range.first;it!=range.second;++it)
                sum+=it->second;
            (*normals)[pointID] = sum.getNormalized();
            normalsPerPoint.erase(range.first, range.second);
        }
#endif
    }
}

void VoronoiRenderer::generateAndParse(float cellSize, float depthDivisor, float maxDensity, VoronoiGenerationThread * thread)
{
    double minX,minY;
    double maxX,maxY;

    minX=minY=std::numeric_limits<double>::max();
    maxX=maxY=std::numeric_limits<double>::min();

    geom::TerrainPointsList::const_iterator it;
    geom::TerrainPointsList::const_iterator end = m_points.end();

    for(it=m_points.begin();it!=end;++it)
    {
        const geom::Point3D& po = it->p;
        if(po.v[0]<minX)
            minX = po.v[0];
        if(po.v[2]<minY)
            minY = po.v[2];
        if(po.v[0]>maxX)
            maxX = po.v[0];
        if(po.v[2]>maxY)
            maxY = po.v[2];
    }

    double pointsWidth = maxX-minX;
    double pointsHeight= maxY-minY;
    long int numPoints = m_points.size();

    printf("Puntos: %li, en rango: (%f,%f), (%f,%f)\n", numPoints, minX, maxX, minY,maxY);

    double pointsPerSquareUnit = numPoints / (pointsWidth * pointsHeight);

    double MAX_POINTS_PER_IMAGE = m_width*m_height*maxDensity/1000.0;

    double squareUnitsPerImage = MAX_POINTS_PER_IMAGE / pointsPerSquareUnit; // (points/image) / (points/unit) = units/image
    double imageSide = sqrt(squareUnitsPerImage);

    int numImagesHorizontal = (int)std::ceil(pointsWidth/imageSide);
    int numImagesVertical = (int)std::ceil(pointsHeight/imageSide);

    double meanPointsPerImage = numPoints / (numImagesHorizontal * numImagesVertical);
    double pointsPerRow = sqrt(meanPointsPerImage);

    printf("Imagenes que hacen falta: %ix%i\n", numImagesHorizontal,numImagesVertical);

    double imageWidth = pointsWidth / numImagesHorizontal;
    double imageHeight = pointsHeight / numImagesVertical;


    double squareSide = cellSize*(0.1+std::max(imageWidth/pointsPerRow, imageHeight/pointsPerRow));
    printf("Square side :%f\n",squareSide);

    int numImagesTotal = numImagesHorizontal * numImagesVertical;

    for(int x = 0; x<numImagesHorizontal;++x)
        for(int y = 0;y<numImagesVertical;++y)
        {
        if(thread->mustEnd())
            return;
        double minImageX = minX + imageWidth * x;
        double minImageY = minY + imageHeight * y;
        double maxImageX = minX + imageWidth * (x+1+2*squareSide); // las imagenes se solapan el tamanyo que tengan los cuadrados
        double maxImageY = minY + imageHeight * (y+1+2*squareSide);
        printf("foto: (%f,%f) -> (%f,%f)\n", minImageX,minImageY, maxImageX,maxImageY);
        int currentImage = x*numImagesVertical + y;
        thread->doEmitStatusUpdate(currentImage*100.0f/numImagesTotal,currentImage, numImagesTotal);
        render(minImageX,maxImageX,minImageY,maxImageY, squareSide, depthDivisor);
        unsigned int * pixels = getRenderedImage();
        voronoi::VoronoiParser parser(pixels,m_width,m_height);
        parser.parse(&m_parsedFaces);
    }
    thread->doEmitStatusUpdate(100.0f,numImagesTotal, numImagesTotal);
}


void VoronoiRenderer::render(double minx, double maxx, double miny, double maxy, double squareSide, float depthDivisor)
{
    int previousViewport[4];
    glGetIntegerv(GL_VIEWPORT, previousViewport);
    glViewport(0, 0, m_width,m_height);
    glDisable(GL_BLEND);

    glEnable(GL_DEPTH_TEST);

    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    glOrtho(minx,maxx,miny,maxy,-1.1,100.1);
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity();

    // Dibujamos todo el contenido
    m_fbo.beginCapture();
    m_shader->bind();

    glClearColor(1.0f,1.0f,1.0f,1.0f);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    glTranslatef(0.0,0.0,-14.0);

    float center[2];
    m_shader->setUniform1f("maxPointsDistance",depthDivisor);

    glBegin(GL_QUADS);
    numPoints = m_points.size();
    int pointsInside = 0;
    for(int i=0;i<numPoints;i++)
    {
        center[0] = m_points.at(i).p.v[0];
        center[1] = m_points.at(i).p.v[2];

        if(center[0] < minx-squareSide || center[0]> maxx+squareSide)
            continue;
        if(center[1] < miny-squareSide || center[1]> maxy+squareSide)
            continue;
        pointsInside++;

        float red = (((i /128) / 128) << 1 )/ 256.0;
        float green = (((i /128) % 128) << 1 )/ 256.0;
        float blue = (i % 128 << 1) / 256.0;

        glNormal3f(center[0], center[1],0.0f);
        glColor3f(red,green,blue);

        glVertex2f(center[0] - squareSide, center[1] - squareSide);
        glVertex2f(center[0] + squareSide, center[1] - squareSide);
        glVertex2f(center[0] + squareSide, center[1] + squareSide);
        glVertex2f(center[0] - squareSide, center[1] + squareSide);
    }
    printf("Points inside view:%i\n",pointsInside);
    glEnd();

    m_shader->unbind();
    m_fbo.endCapture();
    imageRendered = true;
    glViewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]);
}

unsigned int * VoronoiRenderer::getRenderedImage()
{
    if(!imageRendered)
        return NULL;
    m_fbo.beginCapture();
    glReadPixels(0,0,m_width,m_height,GL_RGBA,GL_FLOAT,renderedImage);
    m_fbo.endCapture();

    unsigned char r,g,b;
    float fr,fg,fb;
    for(int y=0;y<m_height;++y)
        for(int x=0;x<m_width;++x)
        {
        fr = renderedImage[4*(x+m_width*y)] * 128.0f;
        fg = renderedImage[4*(x+m_width*y)+1] * 128.0f;
        fb = renderedImage[4*(x+m_width*y)+2] * 128.0f;

        r = lroundf(fr);
        g = lroundf(fg);
        b = lroundf(fb);
        convertedImage[x+ m_width*y] =  r*128*128 + g*128 + b;
    }

    return convertedImage;
}

void VoronoiGenerationThread::run()
{
    //QGLFormat fmt;
    //fmt.setDepth(true);
    //m_context.setFormat(fmt);
    //m_context.create();
    printf("\n\nCurrent error after m_context creation: %i\n\n",glGetError());

   // m_context.makeCurrent();
    m_parent->makeCurrent();
    m_parent->disableRender();

    m_renderer->generateAndParse(m_cellSize,m_depthDivisor,m_maxDensity, this);
    if(!m_mustEnd)
        m_parent->retrieveTriangulated(m_renderer);
    m_parent->enableRender();
    m_parent->doneCurrent();
    delete m_renderer;
   // m_context.doneCurrent();
   // m_context.reset();
}

void VoronoiGenerationThread::tryToEnd()
{
    m_mustEnd = true;
}
